//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <stdio.h>
#include <windows.h>
#include "win32_serial.h"
#include "../serialio.h"


typedef struct serial_res_t {
    HANDLE          _hCom;
    OVERLAPPED      _ro, _wo, _waitcomm;
    DCB             _dcb;
    COMMTIMEOUTS    _co;
    BOOL            blocking;
} serial_res_t;


serial_res_t * win32_serial_init(INT comport)
{
    serial_res_t *io = new serial_res_t;
    if (io == NULL) { return NULL; }

    io->_hCom = INVALID_HANDLE_VALUE;
    memset(&io->_ro, 0, sizeof(OVERLAPPED));
    memset(&io->_wo, 0, sizeof(OVERLAPPED));
    memset(&io->_waitcomm, 0, sizeof(OVERLAPPED));

    char szComName[10];
    char szPort[5];

    if (comport < 0) { goto _ErrorExit;; }
    _itoa(comport, szPort, 10);
    strcpy(szComName, "COM");
    strcat(szComName, szPort);

    io->blocking = FALSE;

    io->_hCom = CreateFileA(
        szComName,                  // pointer to name of the file
        GENERIC_READ | GENERIC_WRITE, // access mode
        0,                          // comm devices must be opened w/exclusive-access
        NULL,                       // no security attributs
        OPEN_EXISTING,              // comm devices must use OPEN_EXISTING
        FILE_FLAG_OVERLAPPED,       // overlapped I/O
        NULL);                      // hTemplate must be NULL for comm devices

    if (io->_hCom == INVALID_HANDLE_VALUE) {
        // handle the error
        printf("CreateFile() failed[%s], invalid handle value!\n", szComName);
        goto _ErrorExit;
    }

    if (!SetupComm(
        io->_hCom,
        2000,
        2000)) {
        goto _ErrorExit;
    }

    if (!SetCommMask(io->_hCom, EV_RXCHAR)) {
        goto _ErrorExit;
    }

    if (!PurgeComm(
        io->_hCom,
        PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR)) {
        goto _ErrorExit;
    }

    io->_ro.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    io->_wo.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    io->_waitcomm.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    io->_co.ReadIntervalTimeout = 2;
    io->_co.ReadTotalTimeoutMultiplier = 0;
    io->_co.ReadTotalTimeoutConstant = 1;
    io->_co.WriteTotalTimeoutMultiplier = 10;
    io->_co.WriteTotalTimeoutConstant = 200;

    if (!SetCommTimeouts(io->_hCom, &io->_co)) {
        goto _ErrorExit;
    }

    if (!GetCommState(io->_hCom, &io->_dcb)) {
        goto _ErrorExit;
    }

    // Change the DCB structure settings.
    io->_dcb.BaudRate = 115200;             // Current baud
    io->_dcb.fBinary = TRUE;               // Binary mode; no EOF check
    io->_dcb.fParity = TRUE;               // Enable parity checking
    io->_dcb.fOutxCtsFlow = FALSE;         // No CTS output flow control
    io->_dcb.fOutxDsrFlow = FALSE;         // No DSR output flow control
    io->_dcb.fDtrControl = DTR_CONTROL_ENABLE;
                                          // DTR flow control type
    io->_dcb.fDsrSensitivity = FALSE;      // DSR sensitivity
    io->_dcb.fTXContinueOnXoff = TRUE;     // XOFF continues Tx
    io->_dcb.fOutX = FALSE;                // No XON/XOFF out flow control
    io->_dcb.fInX = FALSE;                 // No XON/XOFF in flow control
    io->_dcb.fErrorChar = FALSE;           // Disable error replacement
    io->_dcb.fNull = FALSE;                // Disable null stripping
    io->_dcb.fRtsControl = RTS_CONTROL_ENABLE;
                                          // RTS flow control
    io->_dcb.fAbortOnError = FALSE;        // Do not abort reads/writes on
                                          // error
    io->_dcb.ByteSize = 8;                 // Number of bits/byte, 4-8
    io->_dcb.Parity = NOPARITY;            // 0-4=no,odd,even,mark,space
    io->_dcb.StopBits = ONESTOPBIT;        // 0,1,2 = 1, 1.5, 2

    SetCommState(io->_hCom, &io->_dcb);

    return io;

_ErrorExit:
    if (io->_hCom == INVALID_HANDLE_VALUE) {
        CloseHandle(io->_hCom);
    }
    delete io;
    return NULL;
}

// Fetch a character from the device input buffer, waiting if necessary
INT win32_serial_read(
    serial_res_t *io,
    BYTE *pBuffer,
    UINT Size,
    UINT * pSizeRead)
{
    DWORD dwRead;

    *pSizeRead = 0;
    dwRead = 0;
    if (io->blocking) {
        DWORD dwTmp;
        DWORD toRead = Size;
        while (toRead > 0) {
            BOOL res;
            res = ReadFile(io->_hCom, pBuffer + dwRead, toRead, &dwTmp, &io->_ro);
            if (res) {
                dwRead += dwTmp;
                toRead -= dwTmp;
            }
            else {
                DWORD error = GetLastError();
                if (error == ERROR_IO_PENDING) {
                    BOOL res;
                    DWORD numOfRead;
                    res = GetOverlappedResult(
                            io->_hCom,
                            &io->_ro,
                            &numOfRead,
                            TRUE);
                    if (res) {
                        dwRead += numOfRead;
                    }
                    else {
                        printf("serial panic -> GetOverlappedResult\n");
                        break;
                    }
                }
            }
        }
        *pSizeRead = dwRead;
    }
    else {
        BOOL res;
        res = ReadFile(io->_hCom, pBuffer, Size, &dwRead, &io->_ro);
        if (res) {
            *pSizeRead = dwRead;
        }
        else {
            if(GetLastError() == ERROR_IO_PENDING) {
                res = GetOverlappedResult(
                    io->_hCom,
                    &io->_ro,
                    &dwRead,
                    TRUE);
                if (res) {
                    *pSizeRead = dwRead;
                }
                else {
                    *pSizeRead = 0;
                }
            }
            else {
                *pSizeRead = 0;
            }
        }
    }
    return 0;
}

INT win32_serial_write(
    serial_res_t *io,
    const BYTE *pBuffer,
    UINT Size,
    UINT* pSizeWritten)
{
    *pSizeWritten = 0;
    BOOL fState;
    COMSTAT stat;
    DWORD error;
    DWORD written;

    ClearCommError(io->_hCom, &error, &stat);
    fState = WriteFile(
        io->_hCom,
        pBuffer,
        Size,
        &written,
        &io->_wo);
    if (fState) {
        *pSizeWritten = written;
    }
    else {
        if(GetLastError() == ERROR_IO_PENDING) {
            BOOL res = GetOverlappedResult(
                io->_hCom,
                &io->_wo,
                &written,
                TRUE);
            if (res) {
                *pSizeWritten = written;
            }
            else {
                *pSizeWritten = 0;
            }
        }
        else {
            *pSizeWritten = 0;
        }
    }
    return 0;
}

void win32_serial_uninit(
    serial_res_t *io)
{
    PurgeComm(
        io->_hCom,
        PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);

    if(io->_hCom) {
        CloseHandle(io->_hCom);
        io->_hCom = INVALID_HANDLE_VALUE;
    }
    if(io->_ro.hEvent) {
        CloseHandle(io->_ro.hEvent);
    }
    if(io->_wo.hEvent) {
        CloseHandle(io->_wo.hEvent);
    }
    if(io->_waitcomm.hEvent) {
        CloseHandle(io->_waitcomm.hEvent);
    }
    delete io;

}

void win32_serial_FlushOutput(
    serial_res_t *io)
{
    PurgeComm(io->_hCom, PURGE_TXCLEAR);
}

void win32_serial_FlushInput(
    serial_res_t *io)
{
    PurgeComm(io->_hCom, PURGE_RXCLEAR);
}

void win32_serial_abort(
    serial_res_t *io)
{
    SetEvent(io->_waitcomm.hEvent);
    CancelIo(io->_hCom);
}

void win32_serial_WaitForEvent(
    serial_res_t *io)
{
    BOOL res;
    DWORD dwMask;
    res = WaitCommEvent(io->_hCom, &dwMask, &io->_waitcomm);
    if (!res) {
        DWORD dwRes = WaitForSingleObject(io->_waitcomm.hEvent, INFINITE);
        if (dwRes == WAIT_OBJECT_0) {
        }
        /*
        if(GetLastError() == ERROR_IO_PENDING) {
            DWORD tmp;
            res = GetOverlappedResult(
                io->_hCom,
                &io->_waitcomm,
                &tmp,
                TRUE);
        }
        */
    }
}

DWORD win32_serial_GetInQueueSize(
    serial_res_t *io)
{
    COMSTAT     stat;
    DWORD       error;

    if (ClearCommError(io->_hCom, &error, &stat)) {
        return stat.cbInQue;
    }
    return 0;
}

void win32_serial_SetCommBlock(
    serial_res_t *io,
    INT bBlock)
{
    io->blocking = bBlock ? TRUE : FALSE;
}

void win32_serial_SetCommTimeouts(
    serial_res_t *io,
    SERIAL_TIMEOUTS *timeouts)
{
    io->_co.ReadIntervalTimeout = timeouts->ReadIntervalTimeout;
    io->_co.ReadTotalTimeoutMultiplier = timeouts->ReadTotalTimeoutMultiplier;
    io->_co.ReadTotalTimeoutConstant = timeouts->ReadTotalTimeoutConstant;
    io->_co.WriteTotalTimeoutMultiplier = timeouts->WriteTotalTimeoutMultiplier;
    io->_co.WriteTotalTimeoutConstant = timeouts->WriteTotalTimeoutConstant;
    SetCommTimeouts(io->_hCom, &io->_co);
}

void win32_serial_SetSerialInfo(
    serial_res_t *io,
    SerialInfo *info)
{
    if (!GetCommState(io->_hCom, &io->_dcb)) {
        return;
    }

    // Change the DCB structure settings.
    if (info->baud == NUM_SERIAL_BAUD_460800) {
        io->_dcb.BaudRate = 460800;
    }
    else if (info->baud == NUM_SERIAL_BAUD_921600) {
        io->_dcb.BaudRate = 921600;
    }
    else { io->_dcb.BaudRate = 115200; }   // Current baud
    io->_dcb.fBinary = TRUE;               // Binary mode; no EOF check
    io->_dcb.fParity = TRUE;               // Enable parity checking
    io->_dcb.fOutxCtsFlow = FALSE;         // No CTS output flow control
    io->_dcb.fOutxDsrFlow = FALSE;         // No DSR output flow control
    io->_dcb.fDtrControl = DTR_CONTROL_ENABLE;
                                           // DTR flow control type
    io->_dcb.fDsrSensitivity = FALSE;      // DSR sensitivity
    io->_dcb.fTXContinueOnXoff = TRUE;     // XOFF continues Tx
    io->_dcb.fOutX = FALSE;                // No XON/XOFF out flow control
    io->_dcb.fInX = FALSE;                 // No XON/XOFF in flow control
    io->_dcb.fErrorChar = FALSE;           // Disable error replacement
    io->_dcb.fNull = FALSE;                // Disable null stripping
    io->_dcb.fRtsControl = RTS_CONTROL_ENABLE;
                                          // RTS flow control
    io->_dcb.fAbortOnError = FALSE;        // Do not abort reads/writes on
                                          // error
    io->_dcb.ByteSize = 8;                 // Number of bits/byte, 4-8
    io->_dcb.Parity = NOPARITY;            // 0-4=no,odd,even,mark,space
    io->_dcb.StopBits = ONESTOPBIT;        // 0,1,2 = 1, 1.5, 2

    SetCommState(io->_hCom, &io->_dcb);
}

